﻿// Inner Fire 游戏引擎库
// 相机类
//
// Copyright (c) 2024 by 尔西大帝. All rights reserved.
//
// 这个文件用于各种相机。根据《DirectX12游戏开发实战》中的工具类修改。
// 原作者：Frank Luna
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-11-20

#include "dx_camera.h"
#include <graphics/imath.h>

using namespace DirectX;

namespace ifire::dx {

///////////////////////////////////////////////////////////////////////////////
// SphericalCamera
///////////////////////////////////////////////////////////////////////////////
#pragma region SphericalCamera
DxSphericalCamera::DxSphericalCamera() {
  // 设置相机的初始位置
  coord_.theta = 1.5f * XM_PI;
  coord_.phi = XM_PIDIV4;
  coord_.radius = 10.0f;
}

void DxSphericalCamera::OnRotate(float move_x, float move_y) {
  // 根据鼠标的移动距离计算旋转角度，并令每个像素都按此角度的1/4旋转
  float dx = XMConvertToRadians(0.25f * move_x);
  float dy = XMConvertToRadians(0.25f * move_y);

  // 更新角色
  coord_.theta += dx;
  coord_.phi += dy;

  // 限制角度的范围
  coord_.phi = imath::Clamp(coord_.phi, 0.1f, XM_PI - 0.1f);
}

// 函数：根据输入的x, y平移position，沿着相机的朝向
static XMVECTOR MovePositionAlongDirection(XMVECTOR position,
    XMVECTOR direction, float x, float y, float moveSpeed = 1.0f) {
  // 计算右向量（direction绕世界空间的上向量(0,1,0)旋转90度）
  // 使用叉乘计算右向量，假设世界空间的上向量为(0,1,0)
  XMVECTOR upWorld = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
  XMVECTOR right = XMVector3Cross(upWorld, direction);
  right = XMVector4Normalize(right);

  // 如果direction与世界空间的上向量平行，叉乘结果可能为零，需要特殊处理
  if (XMVectorGetX(XMVector3LengthSq(right)) < 1e-6f) {
    // 假设direction不是完全向上或向下，可以选择一个默认的右向量
    right = XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f);
  }

  // 计算上向量，通过右向量绕direction旋转得到
  XMVECTOR trueUp = XMVector3Cross(direction, right);
  trueUp = XMVector4Normalize(trueUp);

  // 计算平移向量
  XMVECTOR translation = XMVectorScale(right, x * moveSpeed) +
                         XMVectorScale(trueUp, y * moveSpeed);

  // 更新位置
  XMVECTOR newPosition = position + translation;

  return newPosition;
}

void DxSphericalCamera::OnOffset(float move_x, float move_y) {
  // 获得坐标
  auto pos_f3 = coord_.GetCartesian();
  auto pos = XMLoadFloat3(&pos_f3);
  // 通过OFFSET计算当前的相机朝向
  auto offset = XMLoadFloat3(&offset_);

  // 计算照向向量
  auto fixed_pos = pos + offset;
  // 因为原点是0，所以这里的offset就直接等于视点。
  auto dir = XMVector3Normalize(offset - fixed_pos);

  // 进行平移
  auto new_pos =
      MovePositionAlongDirection(fixed_pos, dir, move_x, -move_y, offset_rate_);
  // 得到差值
  offset += new_pos - fixed_pos;

  // 得到新的OFFSET
  XMStoreFloat3(&offset_, offset);
}

void DxSphericalCamera::OnWheelZoom(int delta) {
  // 更新可视范围
  float value = static_cast<float>(delta) * -zoom_rate_;
  coord_.radius += value;

  // 限制可视范围的半径
  coord_.radius = imath::Clamp(coord_.radius, min_radius, max_radius);
}

void DxSphericalCamera::SetCoord(float theta, float phi, float radius) {
  coord_.theta = theta;
  coord_.phi = phi;
  coord_.radius = radius;
}

IVec3 DxSphericalCamera::GetCoord() {
  return IVec3(coord_.theta, coord_.phi, coord_.radius);
}

void DxSphericalCamera::SetRadius(float radius) {
  SetCoord(coord_.theta, coord_.phi, radius);
}

void DxSphericalCamera::UpdateViewMatrix() {
  // 将球面坐标置换为笛卡尔坐标
  float sin_phi = sinf(coord_.phi);
  float x = coord_.radius * sin_phi * cosf(coord_.theta);
  float z = coord_.radius * sin_phi * sinf(coord_.theta);
  float y = coord_.radius * cosf(coord_.phi);

  XMVECTOR offset =
      XMLoadFloat3(&offset_); // XMVectorSet(10.0f, 0.0f, 0.0f, 0.0f);

  // 构建观察矩阵
  XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
  pos += offset;
  XMStoreFloat3(&position_, pos); // 将保存矩阵

  XMVECTOR target = XMVectorZero();
  target += offset;
  XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

  view_ = XMMatrixLookAtLH(pos, target, up);
}

#pragma endregion

///////////////////////////////////////////////////////////////////////////////
// Camera
///////////////////////////////////////////////////////////////////////////////
#pragma region Camera
DxCamera::DxCamera() { SetLens(0.25f * PI, 1.0f, 1.0f, 1000.0f); }

DxCamera::~DxCamera() {}

void DxCamera::SetPosition(const XMFLOAT3& v) {
  position_ = v;
  SetRefresh();
}

void DxCamera::SetLens(float fovY, float aspect, float zn, float zf) {
  // cache properties
  fov_y_ = fovY;
  aspect_ = aspect;
  near_z_ = zn;
  far_z_ = zf;

  near_window_height_ = 2.0f * near_z_ * tanf(0.5f * fov_y_);
  far_window_height_ = 2.0f * far_z_ * tanf(0.5f * fov_y_);

  XMMATRIX P = XMMatrixPerspectiveFovLH(fov_y_, aspect_, near_z_, far_z_);
  XMStoreFloat4x4(&projection_, P);
}

void DxCamera::Strafe(float d) {
  // mPosition += d*mRight
  XMVECTOR s = XMVectorReplicate(d);
  XMVECTOR r = XMLoadFloat3(&right_);
  XMVECTOR p = XMLoadFloat3(&position_);
  XMStoreFloat3(&position_, XMVectorMultiplyAdd(s, r, p));

  SetRefresh();
}

void DxCamera::Walk(float d) {
  // mPosition += d*mLook
  XMVECTOR s = XMVectorReplicate(d);
  XMVECTOR l = XMLoadFloat3(&look_);
  XMVECTOR p = XMLoadFloat3(&position_);
  XMStoreFloat3(&position_, XMVectorMultiplyAdd(s, l, p));

  SetRefresh();
}

void DxCamera::Pitch(float angle) {
  // Rotate up and look vector about the right vector.

  XMMATRIX R = XMMatrixRotationAxis(XMLoadFloat3(&right_), angle);

  XMStoreFloat3(&up_, XMVector3TransformNormal(XMLoadFloat3(&up_), R));
  XMStoreFloat3(&look_, XMVector3TransformNormal(XMLoadFloat3(&look_), R));

  SetRefresh();
}

void DxCamera::RotateY(float angle) {
  // Rotate the basis vectors about the world y-axis.

  XMMATRIX R = XMMatrixRotationY(angle);

  XMStoreFloat3(&right_, XMVector3TransformNormal(XMLoadFloat3(&right_), R));
  XMStoreFloat3(&up_, XMVector3TransformNormal(XMLoadFloat3(&up_), R));
  XMStoreFloat3(&look_, XMVector3TransformNormal(XMLoadFloat3(&look_), R));

  SetRefresh();
}

void DxCamera::SetCamera(FXMVECTOR position, FXMVECTOR rotation) {
  // 存储位置（XMVECTOR -> XMFLOAT3）
  XMStoreFloat3(&position_, position);

  // 创建旋转矩阵（四元数 -> 矩阵）
  XMMATRIX rotationMatrix = XMMatrixRotationQuaternion(rotation);

  // 初始化基础轴向量
  XMVECTOR baseRight = XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f);
  XMVECTOR baseUp = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
  XMVECTOR baseLook = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f);

  // 应用旋转变换
  XMVECTOR rotatedRight = XMVector3TransformNormal(baseRight, rotationMatrix);
  XMVECTOR rotatedUp = XMVector3TransformNormal(baseUp, rotationMatrix);
  XMVECTOR rotatedLook = XMVector3TransformNormal(baseLook, rotationMatrix);

  // 标准化并存储结果
  XMStoreFloat3(&right_, XMVector3Normalize(rotatedRight));
  XMStoreFloat3(&up_, XMVector3Normalize(rotatedUp));
  XMStoreFloat3(&look_, XMVector3Normalize(rotatedLook));

  SetRefresh();
}

void DxCamera::UpdateViewMatrix() {
  if (is_dirty_) {
    XMVECTOR R = XMLoadFloat3(&right_);
    XMVECTOR U = XMLoadFloat3(&up_);
    XMVECTOR L = XMLoadFloat3(&look_);
    XMVECTOR P = XMLoadFloat3(&position_);

    // Keep camera's axes orthogonal to each other and of unit length.
    L = XMVector3Normalize(L);
    U = XMVector3Normalize(XMVector3Cross(L, R));

    // U, L already ortho-normal, so no need to normalize cross product.
    R = XMVector3Cross(U, L);

    // Fill in the view matrix entries.
    float x = -XMVectorGetX(XMVector3Dot(P, R));
    float y = -XMVectorGetX(XMVector3Dot(P, U));
    float z = -XMVectorGetX(XMVector3Dot(P, L));

    XMStoreFloat3(&right_, R);
    XMStoreFloat3(&up_, U);
    XMStoreFloat3(&look_, L);

    view_(0, 0) = right_.x;
    view_(1, 0) = right_.y;
    view_(2, 0) = right_.z;
    view_(3, 0) = x;

    view_(0, 1) = up_.x;
    view_(1, 1) = up_.y;
    view_(2, 1) = up_.z;
    view_(3, 1) = y;

    view_(0, 2) = look_.x;
    view_(1, 2) = look_.y;
    view_(2, 2) = look_.z;
    view_(3, 2) = z;

    view_(0, 3) = 0.0f;
    view_(1, 3) = 0.0f;
    view_(2, 3) = 0.0f;
    view_(3, 3) = 1.0f;

    is_dirty_ = false;
  }
}

#pragma endregion

} // namespace ifire::dx